package com.huxi.lang.io;

import java.io.Closeable;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import com.huxi.lang.text.TextUtils;

/**
 * ZIP文件流内容读取
 * 
 * @author jian.wu
 *
 */
public class ZipReader implements Closeable {

	private ZipInputStream zip;
	private boolean closed;

	public ZipReader(InputStream stream) {
		this.zip = new ZipInputStream(Objects.requireNonNull(stream));
		this.closed = false;
	}

	public boolean isClosed() {
		return closed;
	}

	@Override
	public void close() throws IOException {
		if (!closed) {
			closed = true;
			try {
				zip.closeEntry();
			} catch (Exception en) {
				// 跳过关闭条目异常
			}
			zip.close();
		}
	}

	/**
	 * 列出ZIP内容全部条目路劲
	 * 
	 * @return 条目路劲列表，非空值。
	 * @throws IOException
	 */
	public List<String> listAllPaths() throws IOException {
		return readAll(this::alwaysTrue);
	}

	private boolean alwaysTrue(InputStream stream, String path) {
		return true;
	}

	/**
	 * 读取ZIP内容全部内容
	 * 
	 * @param predicate 条目过滤函数，非空值。
	 *                  <ul>
	 *                  <li>InputStream: 条目内容；文件时为字节输入流，为目录时则为空值。</li>
	 *                  <li>String: 条目路劲，非空。</li>
	 *                  </ul>
	 * @return 条目路劲列表，非空值。
	 * @throws IOException
	 */
	public List<String> readAll(BiPredicate<InputStream, String> predicate) throws IOException, RuntimeException {
		Objects.requireNonNull(predicate);
		ArrayList<String> list = new ArrayList<>();
		try {
			ZipEntry entry = null;
			while ((entry = zip.getNextEntry()) != null) {
				String path = entry.getName();
				if (predicate.test(!entry.isDirectory() ? new ZipEntryInputStream(zip, entry) : null, path)) {
					list.add(path);
				}
//				zip.closeEntry();
			}
		} catch (IOException en) {
			throw en;
		} catch (IORuntimeException en) {
			throw en.toIOException();
		} catch (Exception en) {
			throw en;
		} finally {
			try {
				zip.closeEntry();
			} catch (IOException en) {
				// 条目内容已读取结束，忽略此异常
			}
		}
		return list;
	}

	/**
	 * 列举出全部文件路劲
	 * 
	 * @return 文件路劲列表，非空值。
	 * @throws IOException
	 */
	public List<String> listAllFilePaths() throws IOException {
		return readAllFile(this::alwaysTrue);
	}

	/**
	 * 读取ZIP内容全部文件内容，自动跳过目录条目。
	 * 
	 * @param predicate 条目过滤函数，非空值。
	 *                  <ul>
	 *                  <li>InputStream: 条目内容，非空。</li>
	 *                  <li>String: 条目路劲，非空。</li>
	 *                  </ul>
	 * @return 条目路劲列表，非空值。
	 * @throws IOException
	 */
	public List<String> readAllFile(BiPredicate<InputStream, String> predicate) throws IOException, RuntimeException {
		Objects.requireNonNull(predicate, "过滤器参数不能为空值");
		ArrayList<String> list = new ArrayList<>();
		try {
			ZipEntry entry = null;
			while ((entry = zip.getNextEntry()) != null) {
				if (!entry.isDirectory()) {
					String path = entry.getName();
					if (predicate.test(new ZipEntryInputStream(zip, entry), path)) {
						list.add(path);
					}
				}
//				zip.closeEntry();
			}
		} catch (IOException en) {
			throw en;
		} catch (IORuntimeException en) {
			throw en.toIOException();
		} catch (Exception en) {
			throw en;
		} finally {
			try {
				zip.closeEntry();
			} catch (IOException en) {
				// 条目内容已读取结束，忽略此异常
			}
		}
		return list;
	}

	/**
	 * 读取ZIP文件内的下一个文件条目，自动跳过目录条目。
	 * 
	 * @param consumer 条目内容处理函数，非空值。
	 *                 <ul>
	 *                 <li>InputStream: 条目为文件时则为非空输入流，为目录时则为空值。</li>
	 *                 <li>String: 条目名称，非空。</li>
	 *                 </ul>
	 * @return 文件条目名称，非空。
	 * @throws IOException          读取ZIP文件内容失败
	 * @throws NullPointerException 非空值参数为空值时
	 * @throws RuntimeException     条目内容处理处理失败
	 */
	public String readNextFile(BiConsumer<InputStream, String> consumer) throws IOException, RuntimeException {
		Objects.requireNonNull(consumer);
		String path = null;
		try {
			ZipEntry entry = null;
			while ((entry = zip.getNextEntry()) != null) {
				if (!entry.isDirectory()) {
					path = entry.getName();
					consumer.accept(new ZipEntryInputStream(zip, entry), path);
					break;
				}
			}
		} catch (IOException en) {
			throw en;
		} catch (IORuntimeException en) {
			throw en.toIOException();
		} catch (Exception en) {
			throw en;
		} finally {
			try {
				zip.closeEntry();
			} catch (IOException en) {
				// 条目内容已读取结束，忽略此异常
			}
		}
		return path;
	}

	/**
	 * 获取文件条目内容
	 * 
	 * @param <T>
	 * @param path    条目路径，非空。
	 * @param creator 条目内容解析器，非空值。
	 * @return 内容解析后结果
	 * @throws IOException          读取ZIP文件内容失败
	 * @throws NullPointerException 非空值参数为空值时
	 * @throws RuntimeException     条目内容处理处理失败
	 */
	public <T> T readNextFile(String path, Function<InputStream, T> creator) throws IOException, RuntimeException {
		TextUtils.requireNonEmpty(path, "文件路劲参数不能为空");
		Objects.requireNonNull(creator, "内容解析器参数不能为空值");
		T result = null;
		try {
			ZipEntry entry = null;
			while ((entry = zip.getNextEntry()) != null) {
				if (entry.isDirectory() || !path.equals(entry.getName())) {
					zip.closeEntry();
				} else {
					result = creator.apply(new ZipEntryInputStream(zip, entry));
					break;
				}
			}
		} catch (IOException en) {
			throw en;
		} catch (IORuntimeException en) {
			throw en.toIOException();
		} catch (Exception en) {
			throw en;
		} finally {
			if (path != null) {
				try {
					zip.closeEntry();
				} catch (IOException en) {
					// 条目内容已读取结束，忽略此异常
				}
			}
		}
		return result;
	}

	/**
	 * 获取文件条目内容
	 * 
	 * @param path    条目路径，非空。
	 * @param charset 内容字符编码（默认UTF8）
	 * @return 条目内容
	 * @throws IOException          读取ZIP文件内容失败
	 * @throws NullPointerException 非空值参数为空值时
	 * @throws RuntimeException     条目内容处理处理失败
	 */
	public String readNextFile(String path, Charset charset) throws IOException, RuntimeException {
		return readNextFile(path, (stream) -> {
			try {
				return IOUtils.getText(stream, charset != null ? charset : StandardCharsets.UTF_8);
			} catch (IOException en) {
				throw new IORuntimeException(en);
			}
		});
	}

	/**
	 * 获取文件条目内容
	 * 
	 * @param path    条目路径，非空。
	 * @param charset 内容字符编码，非空。
	 * @return 条目内容
	 * @throws IOException          读取ZIP文件内容失败
	 * @throws NullPointerException 非空值参数为空值时
	 * @throws RuntimeException     条目内容处理处理失败
	 */
	public String readNextFile(String path, String charset) throws IOException, RuntimeException {
		return readNextFile(path, Charset.forName(charset != null ? charset : ""));
	}

	public class ZipEntryInputStream extends FilterInputStream {

		private final ZipEntry entry;

		private ZipEntryInputStream(ZipInputStream stream, ZipEntry entry) {
			super(stream);
			this.entry = entry;
		}

		public ZipEntry getEntry() {
			return entry;
		}

		public void close() throws IOException {
			((ZipInputStream) in).closeEntry();
		}

	}

}
